﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Commands
{
    /// <summary>
    /// Parse string with commands.
    /// </summary>
    /// <remarks>Command format must be:
    /// commandname1 par1:val1 par2: par3:"adas:3 rte"
    /// commandname2 par1:val28
    /// commandname3 val42
    /// commandname4 val1 val2 val3
    /// 
    /// result will be:
    /// 
    /// Command.
    ///     Name: 'commandname1'
    /// Parameters:
    ///     Name: 'par1', Value: 'val1'
    ///     Name: 'par2', Value: empty
    ///     Name: 'par3', Value: 'adas:3 rte'
    ///     
    /// Command.
    ///     Name: 'commandname2'
    /// Parameters:
    ///     Name: 'par1', Value: 'val28'
    ///     
    /// Command.
    ///     Name: 'commandname3'
    /// Parameters:
    ///     DefaultParameter: 'val42'
    /// </remarks>
    internal class CommandStringParser
    {
        public CommandStringParser(IMethodsManager manager)
        {
            if (manager == null)
                throw new ArgumentNullException();

            _manager = manager;
        }

        /// <summary>
        /// Parse string with several commands, separated by newline.
        /// </summary>
        public IEnumerable<Command> ParseCommandsString(string commandsString)
        {
            var result = new List<Command>();

            if (string.IsNullOrEmpty(commandsString))
                return result;

            try
            {
                var commandsStrings = commandsString.
                    Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

                foreach (var commandString in commandsStrings)
                {
                    var command = _ParseCommand(commandString);
                    if (command != null)
                        result.Add(command);
                }

            }
            catch (Exception ex)
            {

            }

            return result;
        }

        /// <summary>
        /// Parse commands from mail subject. 
        /// </summary>
        internal static IEnumerable<Command> ParseCommandsFromSubject(string subject, string texts)
        {
            if (string.IsNullOrEmpty(subject) || string.IsNullOrEmpty(texts))
                return null;

            // Split text to lines.
            var commandsStrings = texts.
                Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

            var result = new List<Command>();
            foreach (var commandString in commandsStrings)
            {
                // Split line to "words".
                var splittedStr = _SplitCommandStringToWords(commandString);

                // Check that we have only one "word" - default parameter.
                if (splittedStr.Count() != 1)
                    // Otherwise - we cannot extract command from subject.
                    return null;

                result.Add(new Command(subject, commandString));
            }

            return result;
        }

        #region Private members


        private Command _ParseCommand(string commandString)
        {
            return _ParseCommandWithDefaultParameter(commandString) ?? _ParseCommandWithParameters(commandString);
        }

        private Command _ParseCommandWithDefaultParameter(string commandString)
        {
            Command newCommand = null;

            var splittedString = _SplitCommandStringToWords(commandString);

            if(splittedString.Count() == 2)
            {
                if (_IsDefaultParameter(splittedString.First(), splittedString.Last()))
                    newCommand = new Command(splittedString.First(), splittedString.Last());
            }
            
            return newCommand;
        }

        /// <summary>
        /// Check that word is default parameter.
        /// </summary>
        private bool _IsDefaultParameter(string commandName, string parameterString)
        {
            var splittedParameter = parameterString.Split(new char[] { ':' }, 2);

            var result = true;

            // If we have ':' delimiter in parameter string - check that 
            // first part before delimiter is not parameter name.
            if(splittedParameter.Count() > 1)
            {
                result = !_manager.GetCommandParameters(commandName).
                    Any(param => param.Names.Contains(splittedParameter.First()));
            }

            return result;
        }

        /// <summary>
        /// Parse string with command.
        /// </summary>
        /// <remarks>Command format must be:
        /// commandname par1:val1 par2: par3:"adas:3 rte" par4:
        /// result will be:
        /// Command.
        ///     Name: 'commandname'
        /// Parameters:
        ///     Name: 'par1', Value: 'val1'
        ///     Name: 'par2', Value: empty
        ///     Name: 'par3', Value: 'adas:3 rte'
        ///     Name: 'par4', Value: empty
        /// </remarks>
        private static Command _ParseCommandWithParameters(string commandString)
        {
            var splitted = _SplitCommandStringToWords(commandString);

            var command = new Command(splitted.ElementAt(0));

            // Set parameters list.
            for (int i = 1; i < splitted.Count(); i++)
            {
                // Get strings with parameters.
                var parStr = splitted.ElementAt(i).Split(new char[] { ':' }, 2);

                // If this command has no parameter with such name 
                // - add new parameter, otherwise - ignore this string.
                if (parStr.Count() == 2)
                {
                    if (command.Parameters.FirstOrDefault(x => x.Name == parStr[0]) == null)
                        command.Parameters.Add(parStr[0], parStr[1].TrimMatchingQuotes('\"'));
                }
                else if (parStr.Count() == 1)
                {
                    command.Parameters.Add(splitted.ElementAt(i));
                }
            }

            return command;
        }

        /// For input: 'commandname1 par1:val1 par2: par3:"adas:3 rte"' it will return:
        /// ['commandname1', 'par1:val1', 'par2:', 'par3:adas:3 rte']
        private static IEnumerable<string> _SplitCommandStringToWords(string commandString)
        {
            bool inQuotes = false;

            var splitted = commandString.Split(c =>
            {
                if (c == '\"')
                    inQuotes = !inQuotes;

                return !inQuotes && c == ' ';
            })
                .Select(arg => arg.Trim().TrimMatchingQuotes('\"'))
                .Where(arg => !string.IsNullOrEmpty(arg));

            return splitted;
        }



        #endregion

        private IMethodsManager _manager;
    }
}
